package fight

import (
	// "fsd/protobuf"
	// "fsd/protocol"
	"net"
	"sync"
	"sync/atomic"
	"time"
	// "fsd/logger"
	// "go.uber.org/zap"
)

type Conn struct {
	conn        net.Conn
	closeOnce   sync.Once
	closeFlag   int32
	wg          sync.WaitGroup
	recvNetChan chan *WrapMsg
	sendNetChan chan []byte
	getGameChan chan *WrapMsg
	putGameChan chan *WrapMsg
	connInfo    *ConnInfo
	rw          ReadWriter
}

type ConnInfo struct {
	userId int32
}

func NewConn(conn net.Conn) *Conn {
	return &Conn{
		conn:        conn,
		recvNetChan: make(chan *WrapMsg, Srv.RecvNetChanSize),
		sendNetChan: make(chan []byte, Srv.SendNetChanSize),
		getGameChan: make(chan *WrapMsg, Srv.GetGameChanSize),
		rw:          Srv.NewRW(),
	}
}

func (c *Conn) close() {
	c.closeOnce.Do(func() {
		atomic.StoreInt32(&c.closeFlag, 1)
	})
}

func (c *Conn) IsClosed() bool {
	return atomic.LoadInt32(&c.closeFlag) == 1
}

func (c *Conn) startConn() {
	asyncStartChild(&c.wg, c.reader)
	asyncStartChild(&c.wg, c.writer)
	c.handler()
}

func decodeReadBs(bs []byte) (*WrapMsg, error) {
	cmd := GetCmdByBS(bs)
	wm, err := NewC2SWrapMsg(cmd, Srv.GameProtocoler)
	if err != nil {
		// TODO handle err, err times?
		return nil, err
	}
	err = wm.NewDecodeWrapMsg(bs)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

func (c *Conn) reader() {
	defer func() {
		close(c.recvNetChan)
	}()

	for {
		select {
		case <-Srv.exitChan:
			return

		default:
		}

		c.conn.SetReadDeadline(time.Now().Add(Srv.ConnReadTimeout))
		bs, err := c.rw.Read(c.conn)
		if err != nil {
			return
		}
		// 解包
		var wm *WrapMsg
		wm, err = decodeReadBs(bs)
		if err != nil {
			return
		}
		wm.C = c
		c.recvNetChan <- wm
	}
}

func (c *Conn) writer() {
	defer func() {
		c.conn.Close()
	}()

	for {
		select {
		case <-Srv.exitChan:
			return

		case bs, ok := <-c.sendNetChan:
			if !ok {
				return
			}
			c.conn.SetWriteDeadline(time.Now().Add(Srv.ConnWriteTimeout))
			if _, err := c.rw.Write(c.conn, bs); err != nil {
				return
			}
		}
	}
}

func (c *Conn) handler() {
	defer func() {
		c.close()
		close(c.sendNetChan)
		c.wg.Wait()
	}()

	for {
		select {
		case <-Srv.exitChan:
			return

		case wm, ok := <-c.recvNetChan:
			if !ok {
				return
			}
			if !c.handleC2SMsg(wm) {
				return
			}
		case s2cMsg, ok := <-c.getGameChan:
			if !ok {
				return
			}
			if !c.handleS2CMsg(s2cMsg) {
				return
			}
		}
	}
}

func (c *Conn) handleC2SMsg(c2sMsg *WrapMsg) bool {
	switch c2sMsg.Cmd {
	case PT_CMD_LOGIN:
		userId := c2sMsg.Msg.GetLoginReqUserId()
		fightId := c2sMsg.Msg.GetLoginReqFightId()

		c.connInfo = &ConnInfo{
			userId: userId,
		}
		g, ok := GameMgr.Get(fightId)
		if !ok {
			c.sendNetChan <- PreS2CMsgFightLoginAckFail.Bs
			return false
		}
		c.putGameChan = g.C2SMsgChan
		c.putGameChan <- c2sMsg
		return true
	default:
		if c.connInfo == nil || c.putGameChan == nil {
			return false
		}
		switch c2sMsg.Cmd {
		case PT_CMD_HEART_BEAT:
			c.sendNetChan <- PreS2CMsgHeartBeat.Bs
		default:
			c.putGameChan <- c2sMsg
		}
		return true
	}
}

func (c *Conn) handleS2CMsg(s2cMsg *WrapMsg) bool {
	c.sendNetChan <- s2cMsg.Bs
	switch s2cMsg.Cmd {
	case PT_CMD_LOGIN:
		if s2cMsg.Msg.GetLoginAckRet() != PT_ACK_RET_OK {
			return false
		}
	default:
	}
	return true
}
